/******************************************************************************
    myBoard is a port of MacOS X dashboard application for windows.
    Copyright (C) 2007,2008  by Yann ALET
    Email : yann.alet@gmail.com

    myBoard is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    myBoard is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
****************************************************************************/

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.ComponentModel;

namespace myBoard.Framework
{
    public class RotatingContentControl : ContentControl
    {
        #region Fields
        private Viewport3D _viewport3D;
        private MeshGeometry3D _meshGeometry3D;
        private Grid _contentGrid;
        private ContentPresenter _contentPresenter;
        private ScaleTransform3D _scaleTransformModelRatio;
        private PerspectiveCamera _camera;
        private QuaternionRotation3D _quaternionRotation3D;
        private GeometryModel3D _geometryModel3D;
        #endregion

        #region Constructors

        static RotatingContentControl()
        {
            //This OverrideMetadata call tells the system that this element wants to provide a style that is different than its base class.
            //This style is defined in themes\generic.xaml
            DefaultStyleKeyProperty.OverrideMetadata(typeof(RotatingContentControl), new FrameworkPropertyMetadata(typeof(RotatingContentControl)));
        }

        public RotatingContentControl()
        {
        }


        #endregion

        #region Overridded Methods

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            // Get reference of objects in template:
            _viewport3D = GetTemplateChild("Viewport3D") as Viewport3D;
            _meshGeometry3D = GetTemplateChild("MeshGeometry3D") as MeshGeometry3D;
            _geometryModel3D = GetTemplateChild("GeometryModel3D") as GeometryModel3D;
            _contentGrid = GetTemplateChild("_contentGrid") as Grid;
            _contentPresenter = GetTemplateChild("ContentPresenter") as ContentPresenter;
            _scaleTransformModelRatio = GetTemplateChild("_scaleTransformModelRatio") as ScaleTransform3D;
            _camera = GetTemplateChild("_camera") as PerspectiveCamera;
            _quaternionRotation3D = GetTemplateChild("_quaternionRotation3D") as QuaternionRotation3D;

            OnEulerAnglesPropertyChanged(EulerAngles);
            OnViewIn3DPropertyChanged(ViewIn3D);
        }

        protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
        {
            base.OnRenderSizeChanged(sizeInfo);
        }

        #endregion

        #region Properties

        public Viewport3D Viewport3D
        {

            get { return _viewport3D; }
        }

        #region ViewIn3D

        [Bindable(true)]
        [Category("Custom")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public bool ViewIn3D
        {
            get { return (bool)base.GetValue(ViewIn3DProperty); }
            set { base.SetValue(ViewIn3DProperty, value); }
        }

        public static readonly DependencyProperty ViewIn3DProperty = DependencyProperty.Register(
            "ViewIn3D",
            typeof(bool),
            typeof(RotatingContentControl),
            new PropertyMetadata(false, new PropertyChangedCallback(RotatingContentControl.OnViewIn3DPropertyChanged)));

        private static void OnViewIn3DPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if ((d as RotatingContentControl) != null)
            {
                (d as RotatingContentControl).OnViewIn3DPropertyChanged((bool)e.NewValue);
            }
        }

        protected virtual void OnViewIn3DPropertyChanged(bool viewIn3D)
        {
            if (viewIn3D)
            {
                if (_contentGrid != null)
                {
                    _contentGrid.Visibility = Visibility.Hidden;
                }
                if (_viewport3D != null)
                {
                    _viewport3D.Visibility = Visibility.Visible;
                    _geometryModel3D.Material = new DiffuseMaterial(new VisualBrush(this.Content as Visual));
                 }
                Update3DSurfaceSize();
            }
            else
            {
                if (_contentGrid != null)
                {
                    _contentGrid.Visibility = Visibility.Visible;
                }
                if (_viewport3D != null)
                {
                    _viewport3D.Visibility = Visibility.Hidden;
                   _geometryModel3D.Material = null;
                }
            }
        }

        #endregion

        #region EulerAngles

        [Bindable(true)]
        [Category("Custom")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public Thickness EulerAngles
        {
            get { return (Thickness)GetValue(EulerAnglesProperty); }
            set { SetValue(EulerAnglesProperty, value); }
        }

        public static readonly DependencyProperty EulerAnglesProperty = DependencyProperty.Register(
            "EulerAngles",
            typeof(Thickness),
            typeof(RotatingContentControl),
            new PropertyMetadata(new Thickness(0, 0, 0, 0), new PropertyChangedCallback(RotatingContentControl.OnEulerAnglesPropertyChanged)));

        private static void OnEulerAnglesPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if ((d as RotatingContentControl) != null)
            {
                (d as RotatingContentControl).OnEulerAnglesPropertyChanged((Thickness)e.NewValue);
            }
        }

        protected virtual void OnEulerAnglesPropertyChanged(Thickness eulerAngles)
        {
            if (_quaternionRotation3D != null)
            {
                Quaternion quaternion;

                this.ConvertFromEulerToQuaternion(
                    eulerAngles.Left,
                    eulerAngles.Top,
                    eulerAngles.Right,
                    out quaternion);

                _quaternionRotation3D.Quaternion = quaternion;
            }
        }

        #endregion

        #endregion

        #region Private Methods




        private void Update3DSurfaceSize()
        {
            // Check that the objects in the template have been referenced:
            if (_viewport3D != null && _meshGeometry3D != null && _contentGrid != null && _contentPresenter != null)
            {
                // -- Calcul 3D model ratio (Width/Heigh) based on 2D model ratio
                FrameworkElement content = _contentPresenter.Content as FrameworkElement;
                double RW = content.ActualWidth;
                double RH = content.ActualHeight;
                double ratio = RH / RW;
                if (!Double.IsNaN(RW) && !Double.IsNaN(RH) && (_scaleTransformModelRatio != null))
                {
                    _scaleTransformModelRatio.ScaleY = 0.5 * ratio; // Height = ratio
                    _scaleTransformModelRatio.ScaleX = 0.5; // Width = 1 
                }
                _viewport3D.Margin = new Thickness(-RW / 2, -RH / 2, -RW / 2, -RH / 2);
                _viewport3D.Width = 2 * RW;
                _viewport3D.Height = 2 * RH;

                // -- Reposition camera based on the model size
                _camera.Position = new Point3D(0, 0, 1 / Math.Tan(Math.PI / 12));
                _camera.FieldOfView = 30;
            }
        }

        private void ConvertFromEulerToQuaternion(double headingInDegrees, double attitudeInDegrees, double bankInDegrees, out Quaternion quaternion)
        {
            // Convert from degree to radians:
            double heading = headingInDegrees * Math.PI / 180;
            double attitude = attitudeInDegrees * Math.PI / 180;
            double bank = bankInDegrees * Math.PI / 180;

            // Assuming that the angles are in radians.   
            double c1 = Math.Cos(heading / 2);
            double s1 = Math.Sin(heading / 2);
            double c2 = Math.Cos(attitude / 2);
            double s2 = Math.Sin(attitude / 2);
            double c3 = Math.Cos(bank / 2);
            double s3 = Math.Sin(bank / 2);
            double c1c2 = c1 * c2;
            double s1s2 = s1 * s2;

            quaternion = new Quaternion(
                c1c2 * s3 + s1s2 * c3,
                s1 * c2 * c3 + c1 * s2 * s3,
                c1 * s2 * c3 - s1 * c2 * s3,
                c1c2 * c3 - s1s2 * s3);
        }

        #endregion
    }
}
